// Copyright 2012 Google Inc. All Rights Reserved.  -*- C++ -*-
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <chrono>
#include <iomanip>
#include <iostream>
#include <locale>
#include <sstream>
#include <string>
#include <vector>

class NumsWithSpaces : public std::numpunct_byname<char> {
protected:
  virtual char do_thousands_sep() const {
    return ' ';
  }
  virtual std::string do_grouping() const {
    return "\x3";
  }
public:
  NumsWithSpaces(const char* base_locale = "")
    : numpunct_byname(base_locale) {}
};

class BenchmarkGroup {
  struct BenchmarkResult {
    std::string name;
    std::chrono::nanoseconds time;
  };
  std::vector<BenchmarkResult> results_;

public:
  void add(std::string name, std::chrono::nanoseconds time) {
    BenchmarkResult new_result = {std::move(name), time};
    results_.push_back(std::move(new_result));
  }

  ~BenchmarkGroup() {
    size_t max_name_len = 0;
    std::chrono::nanoseconds max_time(0);
    for (const BenchmarkResult& result : results_) {
      if (result.name.size() > max_name_len)
        max_name_len = result.name.size();
      if (result.time > max_time)
        max_time = result.time;
    }

    const std::locale l(std::cout.getloc(), new NumsWithSpaces());
    const std::locale prev_locale = std::cout.imbue(l);

    std::ostringstream max_time_str;
    max_time_str.imbue(l);
    max_time_str << max_time.count();
    const size_t max_time_len = max_time_str.str().size();

    for (const BenchmarkResult& result : results_) {
      std::cout << result.name << ": "
                << std::setw(max_name_len - result.name.size() + max_time_len)
                << std::right << result.time.count()
                << std::left << "ns\n";
    }
    std::cout.imbue(prev_locale);
  }
};

class Benchmark {
  BenchmarkGroup& group_;
  std::string name_;
  std::chrono::nanoseconds runtime_;
  typedef std::chrono::high_resolution_clock::time_point time_point;
  time_point last_start_;

public:
  Benchmark(BenchmarkGroup& group, std::string name)
    : group_(group), name_(std::move(name)), runtime_(0) {}

  void start() {
    if (last_start_ != time_point()) {
      throw std::logic_error("Benchmark already started.");
    }
    last_start_ = std::chrono::high_resolution_clock::now();
  }
  void stop() {
    time_point stop = std::chrono::high_resolution_clock::now();
    if (last_start_ == time_point()) {
      throw std::logic_error("Benchmark wasn't started yet.");
    }
    runtime_ += stop - last_start_;
    last_start_ = time_point();
  }

  ~Benchmark() {
    group_.add(std::move(name_), runtime_);
  }
};

/// The goal of this identity function is to prevent the optimizer
/// from using its knowledge of the input value to optimize
/// computations that use the output value, without itself costing
/// enough to affect benchmark results.  It will need to be improved
/// as optimizers get better.
template<typename T>
T source(volatile T t) {
  return t;
}
